﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;

namespace TicTacToe.Core.AI
{
    public sealed class AIPlayer : Player
    {
        static readonly Random Rand = new Random();

        public AIDifficultyLevel Difficulty { get; private set; }

        //note to self: is always called in separate Task
        protected internal override void OnTurn(TicTacToeGame game)
        {
            var conditions = Conditions.GetNewRuleset(Difficulty);
            var gamefield = AIGameField.FromGame(game);
            var fields = gamefield.GetFreeFields();
            var player = this == game.PlayerOne ? 1 : 2;

            // execute conditions, analyze field
            var scoreList = (from c in conditions
                             from f in fields
                             let s = c(gamefield, player, f.Item1, f.Item2, f.Item3)
                             select new KeyValuePair<Tuple<int, int, int>, int>(f, Conditions.GetScore(s))).ToList();

            // add up field scores, but include chance to miss something (based on difficulty)
            var x = (double)Difficulty;
            var l1 = Math.Pow(x, -0.3E1) / 0.27E2;

            var scores = new Dictionary<Tuple<int, int, int>, int>();
            foreach (var r in scoreList)
            {
                if (Rand.NextDouble() < l1)
                    continue;

                if (scores.ContainsKey(r.Key))
                {
                    var currentScore = scores[r.Key];
                    scores[r.Key] = currentScore + r.Value;
                }
                else
                    scores.Add(r.Key, r.Value);
            }

            // find best field, but include a certain chance to "miss" the best opportunity
            var orderedScores = scores.OrderByDescending(n => n.Value);
            IEnumerable<Tuple<int, int, int>> highscores;

            var l2 = Math.Pow(0.2e1, -x) / (0.2e1 * x + 0.2e1);
            if (Rand.NextDouble() < l2) // miss best, select second best
            {
                var secondHighscores = orderedScores.SkipWhile(n => n.Value == orderedScores.First().Value).OrderByDescending(n=>n.Value);
                highscores = secondHighscores.TakeWhile(n => n.Value == secondHighscores.First().Value).Select(n => n.Key);
            }
            else // choose best
                highscores = orderedScores.TakeWhile(n => n.Value == orderedScores.First().Value).Select(n => n.Key);
            
            var bestField = highscores.Random();

            // create and execute turn
            var turn = new Turn(this, bestField.Item1, bestField.Item2, bestField.Item3);
            game.MakeTurn(turn);
        }

        static string GetName(AIDifficultyLevel level)
        {
            return String.Concat("[AI]", " ", level.ToString());
        }

        public AIPlayer(AIDifficultyLevel level)
            : this(GetName(level), Colors.Firebrick, level)
        { }

        public AIPlayer(string name, Color color, AIDifficultyLevel level)
            : this(name, color, Guid.NewGuid().ToString(), level)
        { }

        public AIPlayer(string name, Color color, string id, AIDifficultyLevel level)
            : base(name, color, id)
        {
            Difficulty = level;
        }
    }
}
